import inspect, os, os.path, sys, cPickle, imp, py_compile, hashlib, gc
from cache import *
from debugtools import *

__all__ = ['PicklableClass']

class PicklableClass(object):
    """
    Makes a class picklable and allows the creation of an instance of this class
    on a remote computer. The source code of the class is saved in a string
    and recorded at runtime on the file system before being dynamically imported.
    This basic class doesn't handle dependencies with external files.
    """
    codedir = CACHEDIR
    
    def __init__(self, myclass, trackmodules = False):
        self.__name__ = self.classname = myclass.__name__
        self.modulename = myclass.__module__
        self.source = inspect.getsource(myclass).strip()
        self.trackmodules = trackmodules
        sha = hashlib.sha1()
        sha.update(self.source)
        self.hash = sha.hexdigest()
        self.class_filename = self.hash + '.py' # new filename (hash)
        self.class_filename = os.path.join(self.codedir, self.class_filename)
        
        # Retrieves the path of the file where the class is defined
        module = sys.modules[self.modulename]
        if hasattr(module, '__file__'):
            # path of the file where the class is defined
            path = os.path.realpath(module.__file__)
        else:
            # otherwise : current path
            path = os.path.realpath(os.getcwd())
        self.dirname, self.filename = os.path.split(path)
        if os.path.splitext(self.filename) == '.pyc':
            self.filename = self.filename[:-1] # real filename 
        
        # the filename of the original file on the distant server
        self.module_filename = os.path.join(self.codedir, self.filename)
        if trackmodules:
            self.get_files()
    
    def file_exists(self):
        return os.path.exists(self.class_filename)
    
    def record(self):
        if not os.path.exists(self.codedir):
            log.debug("creating '%s' folder for code pickling" % self.codedir)
            os.mkdir(self.codedir)
        log.debug("writing file '%s' for class '%s'" % (self.class_filename, self.classname))
        file = open(self.class_filename, 'w')
        file.write(self.source)
        file.close()
    
    def get_files(self):
        """
        Returns the list of the Python modules stored in the same directory as where
        the class module is defined.
        """
        filelist = []
        for dir, dirnames, filenames in os.walk(self.dirname):
            for name in filenames:
                filename = os.path.realpath(name)
                ext = os.path.splitext(filename)[1]
                if ext == '.py':
                    filelist.append(os.path.join(dir, name))
        return filelist
    
    def load(self):
        if self.trackmodules:
            os.chdir(os.path.join(self.codedir, self.hash))
            modulename = os.path.splitext(os.path.basename(self.module_filename))[0]
            __import__(modulename)
            globals()[self.classname] = getattr(sys.modules[modulename], self.classname)
        else:
            # Loads class_filename (hash.py with the source code of the function)
            imp.load_source('class_module', self.class_filename)
            globals()[self.classname] = getattr(sys.modules['class_module'], self.classname)
    
    def __call__(self, *args, **kwds):
        """
        Instanciates an object of the class.
        """
        if self.classname not in globals():
            if not self.file_exists():
                self.record()
            self.load()
#            myclass = getattr(self.class_module, self.classname)
            myclass = globals()[self.classname]
        else:
            myclass = globals()[self.classname]
        return myclass(*args, **kwds)


  